package com.paradogs.framework.annotations.jprotobuf;

import com.baidu.bjf.remoting.protobuf.annotation.Protobuf;
import com.baidu.bjf.remoting.protobuf.annotation.ProtobufClass;
import com.paradogs.framework.annotations._scan.BaseImportSelector;
import com.paradogs.framework.annotations._scan.BaseScanner;
import com.paradogs.framework.server.MessageHeader;
import com.paradogs.common.utils.ProtoUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.type.classreading.MetadataReader;

import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;

/**
 * @author: yumi
 * @date: 2024/4/7  13:46
 * @Description: Jprotobuf 预编译，避免第一次编解码过慢，在项目启动时提前执行一次
 */
@Slf4j
public class JProtobufPrecompileImport extends BaseImportSelector {

    @Override
    public BaseScanner getScanner(BeanFactory beanFactory) {
        return new PrecompileScanner((DefaultListableBeanFactory) beanFactory);
    }

    @Override
    public void handle(BeanDefinition beanDefinition) {}

    /**
     * 预编译
     * ps: 分批预编译：JProtobuf 内部已经做了加载优化，分批加载并没有优化任何的时间
     * 编译总时间在 4s 左右，可以直接异步，等项目启动好应该就差不多了
     */
    @Override
    public void handle(Set<BeanDefinition> beanDefinitions) {
        long s = System.currentTimeMillis();
        CompletableFuture.runAsync(() -> {
            Map<String, Class<?>> map = new LinkedHashMap<>();
            for (BeanDefinition beanDefinition : beanDefinitions) {
                try {
                    Class<?> beanClass = Class.forName(beanDefinition.getBeanClassName());
                    ProtoUtils.decode(ProtoUtils.encode(beanClass.newInstance()), beanClass);
                    if (map.containsKey(beanClass.getSimpleName())) {
                        log.error("存在同名 JProtobuf 文件：[{}], [{}]", map.get(beanClass.getSimpleName()), beanClass);
                    }
                    map.put(beanClass.getSimpleName(), beanClass);
                } catch (Exception e) {}
            }
        }).thenAccept(unused -> {
            log.info("JProtobuf precompile complete: {}s", (System.currentTimeMillis() - s) / 1000.0);
        });
    }

    @Override
    public String[] getScanPackages() {
        try {
            // 获取被注解的类（启动类）的 Class，以及包路径
            String startupClassStr = getImportingClassMetadata().getClassName();
            Class<?> startupClass = Class.forName(startupClassStr);
            String startupPackage = startupClass.getPackage().getName();

            String[] result = new String[]{startupPackage, "com.paradogs"};

            return result;

        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    public static class PrecompileScanner extends BaseScanner {

        public PrecompileScanner(BeanDefinitionRegistry registry) {
            super(registry);
        }

        @Override
        protected boolean isCandidateComponent(MetadataReader metadataReader) {
            if (metadataReader.getClassMetadata().isAbstract()) {
                return false;
            }
            Class<?>[] excludeClasses = new Class[]{MessageHeader.class}; // 手动添加指定类
            if (Arrays.stream(excludeClasses).anyMatch(clz -> metadataReader.getClassMetadata().getClassName().equals(clz.getName()))) {
                return true;
            }
            boolean hasProtobufClass = metadataReader.getAnnotationMetadata().hasAnnotation(ProtobufClass.class.getName());
            if (hasProtobufClass) {return true;}

            try {
                // 参数注解 @Protobuf
                String className = metadataReader.getClassMetadata().getClassName();
                Class<?> clz = Class.forName(className);
                boolean hasProtobufAnnotation = Arrays.stream(clz.getDeclaredFields())
                        .anyMatch(field -> field.isAnnotationPresent(Protobuf.class));
                return hasProtobufAnnotation;
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
                return false;
            }
        }

        @Override
        protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
            return !beanDefinition.isAbstract();
        }

        @Override
        public boolean isNeedRegister() {
            return true;
        }

    }

}
